package com.smartnsoft.fractory;

import java.util.ArrayList;

import org.jfract.abstractBeans.AbstractFractalBean;
import org.jfract.util.Location;
import org.jfract.util.PictureProcessor;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.smartnsoft.droid4me.log.Logger;
import com.smartnsoft.droid4me.log.LoggerFactory;

/**
 * Display the fractal.
 * 
 * @author Willy Noel
 * @since 2011.05.26
 */
public final class EditorPanel
    extends SurfaceView
    implements SurfaceHolder.Callback
{
  public static final String ON_TAP_ACTION = "onTapAction";

  protected final static Logger log = LoggerFactory.getInstance(EditorPanel.class);

  private ArrayList<ImageWithOffset> listImgs = new ArrayList<ImageWithOffset>();

  private boolean clearImgs;

  private int widthNewImComputed;

  private int heightNewImComputed;

  private ArrayList<Path> _graphics = new ArrayList<Path>();

  private Rect rect = null;

  private float rectStartPosX = -1;

  private float rectStartPosY = -1;

  private float rectEndPosX = -1;;

  private float rectEndPosY = -1;;

  private DrawingThread _thread;

  private AbstractFractalBean fractal;

  private int width;

  private int height;

  private PictureProcessor processor;

  private Paint paint;

  private Bitmap image;

  private boolean zoomMode = false;

  private boolean refresh = true;

  private GestureDetector gestureDetector;

  private ImageWithOffset lastImage;

  private ImageWithOffset lastImage2;

  public interface LocationListener
  {
    public void refreshLocation(Location location);
  }

  public interface CanvasListener
  {
    public Bitmap getImageFromCanvas();
  }

  public CanvasListener canvasListener;

  public LocationListener locationListener;

  private static final int ZOOM = 2;

  private static final int DRAG = 1;

  private static final int NONE = 0;

  private float xPosFractal = 0;

  private float yPosFractal = 0;

  private float zoom;

  private Matrix matrix;

  private Matrix matrix2;

  private Matrix savedMatrix;

  private Matrix savedMatrix2;

  private float oldDist;

  private int mode;

  private PointF mid = new PointF();

  private PointF start = new PointF();

  private boolean firstPosition;

  private PointF currentPosition = new PointF();

  private PointF currentPosition2 = new PointF();

  private PointF fractalPosition = new PointF();

  public float getZoom()
  {
    return zoom;
  }
  public PointF getFractalPosition()
  {
    return fractalPosition;
  }

  public void setFractalPosition(PointF fractalPosition)
  {
    this.fractalPosition = fractalPosition;
  }

  public PointF getCurrentPosition()
  {
    return currentPosition;
  }

  public void setCurrentPosition(PointF currentPosition)
  {
    this.currentPosition = currentPosition;
  }

  public CanvasListener getCanvasListener()
  {
    return canvasListener;
  }

  public void setCanvasListener(CanvasListener canvasListener)
  {
    this.canvasListener = canvasListener;
  }

  public void setZoom(float zoom)
  {
    this.zoom = zoom;
  }

  public Bitmap getImage()
  {
    return image;
  }

  public void setImage(Bitmap image)
  {
    this.image = image;
  }

  public float getXpos()
  {
    return xPosFractal;
  }

  public void setXpos(float xPos)
  {
    this.xPosFractal = xPos;
  }

  public float getYpos()
  {
    return yPosFractal;
  }

  public void setYpos(float yPos)
  {
    this.yPosFractal = yPos;
  }

  public boolean isRefresh()
  {
    return refresh;
  }

  public void setRefresh(boolean refresh)
  {
    this.refresh = refresh;
  }

  public void setLocationListener(LocationListener locationListener)
  {
    this.locationListener = locationListener;
  }

  public ArrayList<ImageWithOffset> getListImgs()
  {
    return listImgs;
  }

  public void setListImgs(ArrayList<ImageWithOffset> listImgs)
  {
    this.listImgs = listImgs;
  }

  public boolean isZoomMode()
  {
    return zoomMode;
  }

  public void setZoomMode(boolean zoomMode)
  {
    this.zoomMode = zoomMode;
  }

  public boolean isClearImgs()
  {
    return clearImgs;
  }

  public void setClearImgs(boolean clearImgs)
  {
    this.clearImgs = clearImgs;
  }

  public int getWidthNewImComputed()
  {
    return widthNewImComputed;
  }

  public void setWidthNewImComputed(int widthNewImComputed)
  {
    this.widthNewImComputed = widthNewImComputed;
  }

  public int getHeightNewImComputed()
  {
    return heightNewImComputed;
  }

  public void setHeightNewImComputed(int heightNewImComputed)
  {
    this.heightNewImComputed = heightNewImComputed;
  }

  public EditorPanel(Context context)
  {
    super(context);
    getHolder().addCallback(this);
    _thread = new DrawingThread(getHolder(), this);
  }

  public AbstractFractalBean getFractal()
  {
    return fractal;
  }

  public void setFractal(AbstractFractalBean fractal)
  {
    this.fractal = fractal;
  }

  public int getWidthPanel()
  {
    return width;
  }

  public void setWidth(int width)
  {
    this.width = width;
  }

  public int getHeightPanel()
  {
    return height;
  }

  public void setHeight(int height)
  {
    this.height = height;
  }

  public PictureProcessor getProcessor()
  {
    return processor;
  }

  public void setProcessor(PictureProcessor processor)
  {
    this.processor = processor;
  }

  public void setImage()
  {
    // image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    // canvas = new Canvas(image);
  }

  public void setPaint()
  {
    paint = new Paint();
    paint.setDither(true);
    paint.setColor(Color.GRAY);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeJoin(Paint.Join.ROUND);
    paint.setStrokeCap(Paint.Cap.ROUND);
    paint.setStrokeWidth(1);
  }

  public EditorPanel(final Context context, AbstractFractalBean fractal,
      PictureProcessor processor, int width, int height)
  {
    super(context);
    this.fractal = fractal;
    this.processor = processor;
    this.width = width;
    this.height = height;
    this.image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    // getHolder().addCallback(this);
    _thread = new DrawingThread(getHolder(), this);
    setPaint();
    gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener()
    {
      @Override
      public boolean onSingleTapUp(MotionEvent event)
      {
        context.sendBroadcast(new Intent(EditorPanel.ON_TAP_ACTION));
        return false;
      }
    });
  }

  public EditorPanel(final Context context, AttributeSet attrs, int defStyle)
  {
    super(context, attrs, defStyle);
    // getHolder().addCallback(this);
    _thread = new DrawingThread(getHolder(), this);
    setPaint();
    gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener()
    {
      @Override
      public boolean onSingleTapUp(MotionEvent event)
      {
        context.sendBroadcast(new Intent(EditorPanel.ON_TAP_ACTION));
        return false;
      }
    });
  }

  public EditorPanel(final Context context, AttributeSet attrs)
  {
    super(context, attrs);
    // getHolder().addCallback(this);
    _thread = new DrawingThread(getHolder(), this);

    matrix = new Matrix();
    savedMatrix = new Matrix();
    matrix2 = new Matrix();
    savedMatrix2 = new Matrix();

    setPaint();
    gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener()
    {
      @Override
      public boolean onSingleTapUp(MotionEvent event)
      {
        context.sendBroadcast(new Intent(EditorPanel.ON_TAP_ACTION));
        return false;
      }
    });
  }

  @Override
  public void onDraw(Canvas canvas)
  {
    if (refresh)
    {
      if (listImgs.size() > 0 && clearImgs)
      {
        for (ImageWithOffset imgWo : listImgs)
        {
          canvas.drawBitmap(imgWo.getImg(), imgWo.getXoffset(), imgWo.getYoffset(), null);
        }
      }
      // TODO: Find a other way to keep the fractal displayed
      if (listImgs.size() > 1)
      {

        lastImage = listImgs.get(0);
        lastImage2 = listImgs.get(1);

      }
      if (lastImage != null && lastImage2 != null && listImgs.size() == 0)
      {
        canvas.drawBitmap(lastImage.getImg(), lastImage.xoffset, lastImage.yoffset, null);
        canvas.drawBitmap(lastImage2.getImg(), lastImage2.xoffset, lastImage2.yoffset, null);

      }
      if (rectStartPosX != -1 && rectStartPosY != -1 && rectEndPosX != -1 && rectEndPosY != -1)
      {
        rect = new Rect((int) rectStartPosX, (int) rectStartPosY, (int) rectEndPosX, (int) rectEndPosY);
        canvas.drawRect(rect, paint);
      }
      for (Path path : _graphics)
      {
        // canvas.drawPoint(graphic.x, graphic.y, mPaint);
        canvas.drawPath(path, paint);
      }
      if (clearImgs)
      {
        listImgs.clear();
        // canvasListener.getImageFromCanvas();
      }
      clearImgs = false;
      processor.notifyThread();
      firstPosition = true;
    }
    else
    {
      if (lastImage != null && lastImage2 != null && (mode == DRAG || mode == ZOOM))
      {

        if (firstPosition)
        {
          firstPosition = false;
          matrix2.setTranslate(lastImage.xoffset, lastImage2.yoffset);
          currentPosition2.set(0, lastImage2.yoffset);
          savedMatrix2.set(matrix2);
        }

        canvas.drawBitmap(lastImage.getImg(), matrix, null);
        canvas.drawBitmap(lastImage2.getImg(), matrix2, null);
      }
    }

  }

  @Override
  public boolean onTouchEvent(MotionEvent event)
  {
    gestureDetector.onTouchEvent(event);
    synchronized (_thread.getSurfaceHolder())
    {
      switch (event.getAction() & MotionEvent.ACTION_MASK)
      {
      case MotionEvent.ACTION_DOWN:
        // canvasListener.getImageFromCanvas();
        savedMatrix.set(matrix);
        savedMatrix2.set(matrix2);
        start.set(event.getX(), event.getY());
        currentPosition.set(0, 0);
        currentPosition2.set(0, 0);
        log.debug("mode=DRAG");
        mode = DRAG;
        break;
      case MotionEvent.ACTION_POINTER_DOWN:
        log.debug("ACTION POINTER DOWN");

        oldDist = spacing(event);
        log.debug("oldDist=" + oldDist);
        if (oldDist > 10f)
        {
          savedMatrix.set(matrix);
          savedMatrix2.set(matrix2);
          midPoint(mid, event);
          mode = ZOOM;
          log.debug("mode=ZOOM");
        }
      case MotionEvent.ACTION_MOVE:

        if (mode == DRAG)
        {
          log.debug("Xbitmap = '" + currentPosition.x + "'" + "Ybitmap = '" + currentPosition.y + "'");

          // log.debug("DRAG");
          matrix.set(savedMatrix);
          matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
          matrix2.set(savedMatrix2);
          matrix2.postTranslate(event.getX() - start.x, (event.getY() - start.y));
          currentPosition.set(event.getX() - start.x, event.getY() - start.y);
          currentPosition2.set(event.getX() - start.x, event.getY() - start.y);
        }
        else if (mode == ZOOM)
        {
          log.debug("ZOOM");
          float newDist = spacing(event);
          log.debug("newDist=" + newDist);
          if (newDist > 10f)
          {
            matrix.set(savedMatrix);
            matrix2.set(savedMatrix2);
            float scale = newDist / oldDist;
            log.debug("scale =" + scale + "'");
            fractal.setZoom(newDist / width);
            log.debug("newZoom =" + newDist / width + "'");
            matrix.postScale(scale, scale, mid.x, mid.y);
            matrix2.postScale(scale, scale, mid.x, mid.y);
          }
        }
        invalidate();
        break;
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_POINTER_UP:
        mode = NONE;
        zoom = fractal.getZoom();
        if (start.x != event.getX())
        {
          fractalPosition.set((fractalPosition.x - ((currentPosition.x / (width * zoom)))), (fractalPosition.y - ((currentPosition.y / (height * zoom)))));

          locationListener.refreshLocation(new Location(fractalPosition.x, fractalPosition.y, zoom));

          matrix.setTranslate(0, 0);
          matrix2.setTranslate(0, lastImage2.yoffset);

          savedMatrix.set(matrix);
          savedMatrix2.set(matrix2);

        }
        log.debug("mode=NONE");
        break;
      }
    }
    return true;
  }

  // Import from http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-6-implementing-the-pinch-zoom-gesture/
  private float spacing(MotionEvent event)
  {
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    return FloatMath.sqrt(x * x + y * y);
  }

  private void midPoint(PointF point, MotionEvent event)
  {
    float x = event.getX(0) + event.getX(1);
    float y = event.getY(0) + event.getY(1);
    point.set(x / 2, y / 2);
  }

  public void surfaceChanged(SurfaceHolder holder, int format, int width,
      int height)
  {

  }

  public void surfaceCreated(SurfaceHolder holder)
  {

    _thread.setRunning(true);
    _thread.start();
  }

  public void surfaceDestroyed(SurfaceHolder holder)
  {

    boolean retry = true;
    _thread.setRunning(false);
    while (retry)
    {
      try
      {
        _thread.join();
        retry = false;
      }
      catch (InterruptedException e)
      {
        // we will try it again and again...
      }
    }
  }

}

class ImageWithOffset
{
  private Bitmap img;

  public int xoffset;

  public int yoffset;

  public float xPos;

  public float yPos;

  public ImageWithOffset(Bitmap img, int xoffset, int yoffset)
  {
    super();
    this.img = img;
    this.xoffset = xoffset;
    this.yoffset = yoffset;
  }

  public Bitmap getImg()
  {
    return img;
  }

  public void setImg(Bitmap img)
  {
    this.img = img;
  }

  public int getXoffset()
  {
    return xoffset;
  }

  public void setXoffset(int xoffset)
  {
    this.xoffset = xoffset;
  }

  public int getYoffset()
  {
    return yoffset;
  }

  public void setYoffset(int yoffset)
  {
    this.yoffset = yoffset;
  }

  public void setYpos(float yPos)
  {
    this.yPos = yPos;
  }

  public float getYpos()
  {
    return yPos;
  }

  public void setXpos(float xPos)
  {
    this.xPos = xPos;
  }

  public float getXpos()
  {
    return xPos;
  }
}

class DrawingThread
    extends Thread
{
  private SurfaceHolder _surfaceHolder;

  private EditorPanel _panel;

  private boolean _run = false;

  public DrawingThread(SurfaceHolder surfaceHolder, EditorPanel panel)
  {
    _surfaceHolder = surfaceHolder;
    _panel = panel;
  }

  public void setRunning(boolean run)
  {
    _run = run;
  }

  public SurfaceHolder getSurfaceHolder()
  {
    return _surfaceHolder;
  }

  @Override
  public void run()
  {
    Canvas c;
    while (_run)
    {
      c = null;
      try
      {
        c = _surfaceHolder.lockCanvas(null);
        synchronized (_surfaceHolder)
        {
          _panel.onDraw(c);
        }
      }
      finally
      {
        // do this in a finally so that if an exception is thrown
        // during the above, we don't leave the Surface in an
        // inconsistent state
        if (c != null)
        {
          _surfaceHolder.unlockCanvasAndPost(c);
        }
      }
    }
  }
}
